#ifndef __tenacitas_log__file_printer__h__
#define __tenacitas_log__file_printer__h__


// =============================================================================
/** @file */

// =============================================================================
/** todo */

// =============================================================================
//                                 C++ Headers
#include <string>
#include <cstdint>
#include <sstream>
#include <fstream>
#include <memory>
#include <chrono>
#include <cstdio>
#include <utility>
#include <type_traits>

// =============================================================================
//                                 3rds Headers
#include <boost/thread/thread.hpp>

// =============================================================================
//                                 Our Headers
#include "level.h"
#include "file_writer.h"

// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */


/** namespace of the organization */
namespace tenacitas {
    namespace log {

        // =========================== Typedefs ===========================
        /** */

        // ============================ Pre-Declarations ==================

        // ============================ Attributes ========================
        /** */

        // ============================ Functions =========================
        /** */

        // ============================ Classes ===========================


        /** 

            @nosubgrouping 
        */
        class default_change_file_policy {

            //     default_change_file_policy - Friends

        public:

            //     default_change_file_policy - Public Internal Types

            /** */
            typedef default_change_file_policy change_file_policy;
            

            /** */
            typedef std::shared_ptr<change_file_policy> ptr; 

            //     default_change_file_policy - Public Constructors

            /** */
            static void config( const std::string & p_dir_log,
                                const std::string & p_pgm_name,
                                int64_t p_pid,
                                uint64_t p_max_file_size = 1024 * 1014 * 10 );
            

            /** Destructor */
            ~default_change_file_policy( ) {}

            /** */
            default_change_file_policy( const default_change_file_policy & ) =
            delete;

            /** */
            default_change_file_policy( default_change_file_policy && ) =
            delete;
            

            /** */
            static ptr create( boost::thread::id p_thread_id ) { 
                return ptr( new change_file_policy( p_thread_id ) ); 
            }

            //     default_change_file_policy - Public Processors

            /** */
            void on_thread_end( ofstream & p_file ) {

                p_file.close( );
                
                const std::string l_log_file_name_ended ( m_current_file_name +
                                                          std::string( ".END" ) );

                std::rename( m_current_file_name.c_str( ),
                             l_log_file_name_ended.c_str( ) );
            }
            
            /** */
            bool change_now( uint64_t p_file_size ) const {
                static const double ninety_per_cent_max =
                    0.9 * static_cast<double>( m_max_file_size );

                const double l_tmp = static_cast<double>( p_file_size );
                
                return ( l_tmp > ninety_per_cent_max );
            }
            
            /** */
            const std::string & file_name( ) {
                std::stringstream l_stream;

                l_stream << m_dir_log << "/" << m_pgm_name << "_" << m_pid << "_";

                if ( m_thread_id == boost::thread::id( ) ) {
                    l_stream << "0";
                }
                else {

                    int64_t l_thread_id = tenacitas::log::th2int( m_thread_id );
        
                    l_stream << std::dec << l_thread_id;
                }

                l_stream << "_" << ++m_file_index;

                l_stream << ".log";

                m_current_file_name = l_stream.str( );
                
                return m_current_file_name;
            }


            //     default_change_file_policy - Public Operators

            /** */
            default_change_file_policy & operator=( const default_change_file_policy & ) =
            delete;

            /** */
            default_change_file_policy & operator=( default_change_file_policy && ) =
            delete;
            

        private:

            //     default_change_file_policy - Private Constructors

            /** */
            default_change_file_policy( boost::thread::id p_thread_id )
                : m_thread_id( p_thread_id ),
                  m_file_index( -1 ) {}
            
            //     default_change_file_policy - Private Attributes

            /** */
            static std::string m_dir_log;

            /** */
            static std::string m_pgm_name;

            /** */
            static int64_t m_pid;

            /** */
            static uint64_t m_max_file_size;

            /** */
            boost::thread::id m_thread_id;

            /** */
            int32_t m_file_index;

            /** */
            std::string m_current_file_name;
            
        };


        
        /**

         */
        template <typename t_change_file_policy,
                  typename t_file_mode = txt_file_mode,
                  uint16_t m_version = 1>
        class file_printer_t {

            //    file_printer_t - Friends
            
        public:

            //    file_printer_t - Public Internal Types

            /** */
            typedef file_printer_t printer;

            /** */
            typedef std::shared_ptr<printer> ptr;

            /** */
            typedef t_change_file_policy change_file_policy; 

            /** */
            typedef typename change_file_policy::ptr change_file_policy_ptr; 

            /** */
            typedef t_file_mode file_mode;

            /** */
            typedef file_writer<file_mode> writer;

            /** */
            typedef typename writer::ptr writer_ptr;
            

            //    file_printer_t - Public Constructors

            /** */
            ~file_printer_t( ) {
                m_change_file_policy->on_thread_end( m_file );
            }

            // /** */
            // static void config( const std::string & p_dir_log,
            //                     const std::string & p_pgm_name,
            //                     int64_t p_pid ) {
            //     m_dir_log = p_dir_log;
            //     m_pgm_name = p_pgm_name;
            //     m_pid = p_pid;
            // }

            /** */
            static ptr create( boost::thread::id p_th_id ) {
                ptr l_printer( new printer( p_th_id ) );  
                return l_printer;
            }
            
            
            /** not allowed */
            file_printer_t( const file_printer_t & ) = delete;

            /** not allowed */
            file_printer_t( file_printer_t && p_r ) = delete;

            //    file_printer_t - Public Processors

            /** */
            template <typename t_data>
            void write ( const t_data & p_data ) {
                m_writer->write( m_file, p_data );
                m_file_size += m_file.tellp( );
            }

            /** */
            void new_line( level p_level, const char * p_file,
                           const char * p_function, int64_t p_line ) {
                if ( m_change_file_policy->change_now( m_file_size ) ) {
                    open_file( );
                }
                    
                m_writer->new_line( m_file, p_level, p_file, p_function, p_line );
                m_file_size += m_file.tellp( );
            }

            
            //    file_printer_t - Public Operators
            
            /** */
            file_printer_t & operator = ( const file_printer_t & ) = delete;

            /** */
            file_printer_t & operator = ( file_printer_t && p_r ) = delete;

        private:

            //    file_printer_t - Private Constructors

            /** */
            file_printer_t( boost::thread::id p_thread_id )
                : m_thread_id( p_thread_id ),
                  m_file( ),
                  m_change_file_policy(
                      change_file_policy::create( p_thread_id ) ),  
                  m_writer( writer::create( ) ),
                  m_file_size ( 0 ) {

                open_file( );
            }
            
            //    file_printer_t - Private Processors

            /** */
            void open_file( ) {

                if( m_file.is_open( ) ) {
                    m_file.close( );
                }

                const char * l_file_name =
                    m_change_file_policy->file_name( ).c_str( );
                

                if ( std::is_same<file_mode, txt_file_mode>::value ) {
                    m_file.open( l_file_name );
                }
                else if ( std::is_same<file_mode, bin_file_mode>::value ) {
                    m_file.open( l_file_name, std::ios::binary );
                }

                m_writer->write( m_file, m_version );
                m_file_size = m_file.tellp( );
            }
            


            //    file_printer_t - Private Attributes

            // /** */
            // static std::string m_dir_log;

            // /** */
            // static std::string m_pgm_name;

            // /** */
            // static int64_t m_pid;

            /** */
            boost::thread::id m_thread_id;

            /** */
            ofstream m_file;

            /** */
            change_file_policy_ptr m_change_file_policy;

            /** */
            writer_ptr m_writer;

            /** */
            uint64_t m_file_size;
        };

    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log;

// template <typename t_change_file_policy_creator,
//           typename t_writer_creator>
// std::string
// file_printer<t_change_file_policy_creator, t_writer_creator>::
// m_dir_log ( "./" );


// template <typename t_change_file_policy_creator,
//           typename t_writer_creator> 
// std::string
// file_printer<t_change_file_policy_creator, t_writer_creator>::
// m_pgm_name( "LOG" );

// template <typename t_change_file_policy_creator,
//           typename t_writer_creator> 
// int64_t
// file_printer<t_change_file_policy_creator, t_writer_creator>::
// m_pid( 0 );


#endif
